use std::convert::TryFrom;

use crate::domain::entities::*;
use crate::repositories::pokemon::{
    InMemoryRepository,
    Repository,
    Insert,
};

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_should_return_the_pokemon_number_otherwise() {
        let number = 25;

        let req = Request {
            number,
            name: String::from("Pikachu"),
            types: vec![String::from("Electric")],
        };

        let mut repo = InMemoryRepository::new();
        let res = execute(&mut repo, req);

        match res {
            Response::Ok(res_number) => assert_eq!(res_number, number),
            _ => unreachable!(),
        };
    }

    #[test]
    fn it_should_return_a_conflict_error() {
        let mut number = PokemonNumber::try_from(25).unwrap();
        let name = PokemonName::try_from(String::from("Pikachu")).unwrap();
        let types = PokemonTypes::try_from(vec![String::from("Electric")]).unwrap();
        let mut repo = InMemoryRepository::new();

        repo.insert(number.clone(), name, types);

        let req = Request {
            number: u16::from(number),
            name: String::from("Charmander"),
            types: vec![String::from("Fire")],
        };

        let res = execute(&mut repo, req);

        match res {
            Response::Conflict => {},
            _ => unreachable!(),
        }
    }
}

struct Request {
    number: u16,
    name: String,
    types: Vec<String>,
}

enum Response {
    Ok(u16),
    BadRequest,
    Conflict,
}

fn execute(repo: &mut dyn Repository, req: Request) -> Response {
    match (
        PokemonNumber::try_from(req.number),
        PokemonName::try_from(req.name),
        PokemonTypes::try_from(req.types),
    ) {
        (Ok(number), Ok(name), Ok(types)) => match repo.insert(number, name, types) {
            Insert::Ok(number) => Response::Ok(u16::from(number)),
            Insert::Conflict => Response::Conflict,
        },
        _ => Response::BadRequest,
    }
}
